/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *  * * * * * * * * * * *
 *            Copyright (C) 2018 Institute of Computing Technology, CAS
 *               Author : Han Shukai (email : hanshukai@ict.ac.cn)
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *  * * * * * * * * * * *
 *         The kernel's entry, where most of the initialization work is done.
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *  * * * * * * * * * * *
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this
 * software and associated documentation files (the "Software"), to deal in the Software
 * without restriction, including without limitation the rights to use, copy, modify,
 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
 * persons to whom the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *  * * * * * * * * * * */

#include <common.h>
#include <asm.h>
#include <asm/unistd.h>
#include <os/loader.h>
#include <os/irq.h>
#include <os/sched.h>
#include <os/lock.h>
#include <os/kernel.h>
#include <os/task.h>
#include <os/string.h>
#include <os/mm.h>
#include <os/time.h>
#include <sys/syscall.h>
#include <screen.h>
#include <printk.h>
#include <assert.h>
#include <type.h>
#include <csr.h>

extern void ret_from_exception();

#define TASKNUM_LOC 0x502001fa
#define A_core 1
#define C_core 1
// Task info array
task_info_t tasks[TASK_MAXNUM];

static void init_jmptab(void)
{
    volatile long (*(*jmptab))() = (volatile long (*(*))())KERNEL_JMPTAB_BASE;

    jmptab[CONSOLE_PUTSTR] = (long (*)())port_write;
    jmptab[CONSOLE_PUTCHAR] = (long (*)())port_write_ch;
    jmptab[CONSOLE_GETCHAR] = (long (*)())port_read_ch;
    jmptab[SD_READ] = (long (*)())sd_read;
    jmptab[QEMU_LOGGING] = (long (*)())qemu_logging;
    jmptab[SET_TIMER] = (long (*)())set_timer;
    jmptab[READ_FDT] = (long (*)())read_fdt;
    jmptab[MOVE_CURSOR] = (long (*)())screen_move_cursor;
    jmptab[PRINT] = (long (*)())printk;
    jmptab[YIELD] = (long (*)())do_scheduler;
    jmptab[MUTEX_INIT] = (long (*)())do_mutex_lock_init;
    jmptab[MUTEX_ACQ] = (long (*)())do_mutex_lock_acquire;
    jmptab[MUTEX_RELEASE] = (long (*)())do_mutex_lock_release;
}

static void init_task_info(void)
{
    char *tasknum = TASKNUM_LOC;
    for (int i = 1; i <= *tasknum; i++)
    {
        memcpy(tasks[i].name, (char *)(0x502001f0 - i * 0x10), 8);
        memcpy(&tasks[i].start_block, (short *)(0x502001f0 - i * 0x10 + 0x8), 2);
        memcpy(&tasks[i].num_block, (short *)(0x502001f0 - i * 0x10 + 0xa), 2);
        memcpy(&tasks[i].offset, (int *)(0x502001f0 - i * 0x10 + 0xc), 4);
    }
    // TODO: [p1-task4] Init 'tasks' array via reading app-info sector
    // NOTE: You need to get some related arguments from bootblock first
}

static void init_pcb_stack(
    ptr_t kernel_stack, ptr_t user_stack, ptr_t entry_point,
    pcb_t *pcb)
{
    /* TODO: [p2-task3] initialization of registers on kernel stack
     * HINT: sp, ra, sepc, sstatus
     * NOTE: To run the task in user mode, you should set corresponding bits
     *     of sstatus(SPP, SPIE, e  tc.).
     */
    regs_context_t *pt_regs =
        (regs_context_t *)(kernel_stack - sizeof(regs_context_t));
    for (int i = 0; i < 32; i++)
    {
        pt_regs->regs[i] = 0;
    }
    pt_regs->regs[4] = pt_regs;
    pt_regs->regs[2] = user_stack;
    pt_regs->regs[1] = exception_handler_entry;
    pt_regs->sstatus = SR_SIE;
    pt_regs->sepc = entry_point;
    printl("the address of regcontext is at %x\n", entry_point);
    /* TODO: [p2-task1] set sp to simulate just returning from switch_to
     * NOTE: you should prepare a stack, and push some values to
     * simulate a callee-saved context.
     */
    switchto_context_t *pt_switchto =
        (switchto_context_t *)((ptr_t)pt_regs - sizeof(switchto_context_t));
    // printl("The context address is at %x\n", pt_switchto);
    for (int i = 0; i < 14; i++)
    {
        pt_switchto->regs[i] = 0;
    }
    pt_switchto->regs[0] = ret_from_exception; // ra寄存器的值

    // printl("the address of entry point is at %x\n", &pt_switchto->regs[0]);
    //  printl("the value of the entry point is %x\n", pt_switchto->regs[0]);

    pcb->kernel_sp = pt_switchto;
    pt_switchto->regs[1] = pcb->kernel_sp;
    // printl("the latest kernel sp is at %x\n", pcb->kernel_sp);
    //  printl("\n");
}

static void init_pcb(void)
{
    /* TODO: [p2-task1] load needed tasks and init their corresponding PCB */
    #ifdef C_core
        int tasknum = 8;
    #else 
        int tasknum = 7;
    #endif

    for (int i = 0; i < tasknum; i++)
    {
        int numpage = 1;
        pcb[i].pid = i + 1;
        pcb[i].wakeup_time = 0;
        pcb[i].status = TASK_READY;
        pcb[i].cursor_x = 0;
        pcb[i].cursor_y = 0;
        pcb[i].kernel_sp = allocKernelPage(numpage);
        pcb[i].user_sp = allocUserPage(numpage);
        initready_queue(&ready_queue, tasknum);
        int taskidx = -1;
        switch (i)
        {
        case 0:
            taskidx = 2;
            break;
        case 1:
            taskidx = 5;
            break;
        case 2:
            taskidx = 6;
            break;
        case 3:
            taskidx = 1;
            break;
        case 4:
            taskidx = 3;
            break;
        case 5:
            taskidx = 8;
            break;
        #ifdef C_core
        case 6:
            taskidx = 7;
            break;
        #endif
        default:
            taskidx = 4;
            break;
        }
        int address = load_task_img(taskidx);
        init_pcb_stack(pcb[i].kernel_sp + numpage* PAGE_SIZE, pcb[i].user_sp + numpage* PAGE_SIZE, address, &pcb[i]);
    }
    /* TODO: [p2-task1] remember to initialize 'current_running' */
    current_running = &pid0_pcb;
    pid0_pcb.list.next = &pcb[0].list;
}

static void init_syscall(void)
{
    // TODO: [p2-task3] initialize system call table.
    syscall[SYSCALL_SLEEP] = (long (*)())do_sleep;
    syscall[SYSCALL_YIELD] = (long (*)())do_scheduler;
    syscall[SYSCALL_WRITE] = (long (*)())screen_write;
    syscall[SYSCALL_CURSOR] = (long (*)())screen_move_cursor;
    syscall[SYSCALL_REFLUSH] = (long (*)())screen_reflush;
    syscall[SYSCALL_GET_TIMEBASE] = (long (*)())get_time_base;
    syscall[SYSCALL_GET_TICK] = (long (*)())get_ticks;
    syscall[SYSCALL_LOCK_INIT] = (long (*)())do_mutex_lock_init;
    syscall[SYSCALL_LOCK_ACQ] = (long (*)())do_mutex_lock_acquire;
    syscall[SYSCALL_LOCK_RELEASE] = (long (*)())do_mutex_lock_release;

    syscall[SYSCALL_THREAD_CREATE] = (long (*)())thread_create;
}

int main(void)
{
    // Init jump table provided by kernel and bios(ΦωΦ)
    init_jmptab();

    // Init task information (〃'▽'〃)
    init_task_info();

    // Init Process Control Blocks |•'-'•) ✧
    init_pcb();
    printk("> [INIT] PCB initialization succeeded.\n");

    // Read CPU frequency (｡•ᴗ-)_
    time_base = bios_read_fdt(TIMEBASE);

    // Init lock mechanism o(´^｀)o
    init_locks();
    printk("> [INIT] Lock mechanism initialization succeeded.\n");

    // Init interrupt (^_^)
    init_exception();
    printk("> [INIT] Interrupt processing initialization succeeded.\n");

    // Init system call table (0_0)
    init_syscall();
    printk("> [INIT] System call initialized successfully.\n");

    // Init screen (QAQ)
    init_screen();
    printk("> [INIT] SCREEN initialization succeeded.\n");

    /* for (int i = 0; i < 3; i++)
     {
         printl("task id is %d\n", i);
         printl("task kernel_sp is 0x%x\n", pcb[i].kernel_sp);
         printl("task user_sp is 0x%x\n", pcb[i].user_sp);
     }
 */
    // TODO: [p2-task4] Setup timer interrupt and enable all interrupt globally
    // NOTE: The function of sstatus.sie is different from sie's
    uint64_t pid0_addr = (regs_context_t *)(pid0_pcb.kernel_sp+ sizeof(switchto_context_t));

    asm volatile(
        "nop\n"
        "mv tp, %0\n"
        :
        :"r"(pid0_addr)
    );
    // Infinite while loop, where CPU stays in a low-power state (QAQQQQQQQQQQQ)
    while (1)
    {
    #ifdef A_core
        // If you do non-preemptive scheduling, it's used to surrender control
        enable_preempt();
        bios_set_timer( get_timer() + TIMER_INTERVAL);
        asm volatile("wfi");

    #else
        // If you do preemptive scheduling, they're used to enable CSR_SIE and wfi
        do_scheduler();
    #endif
    }

    return 0;
}
